#include <iostream>
#include <unistd.h>
#include <string>
#include "sockutil.h"
#include <signal.h>
#include "socks5app.h"
#include <unistd.h>
#include "util/File.h"
#include "json/json.h"
#include <sys/stat.h>
#include <sys/types.h>
#include "util/Logger.h"
#include "auth_type.h"

using std::cout;
using std::cerr;
using std::endl;

int usage()
{
    cout << "可使用以下选项：\n-u 用户名。 \n-P 密码(P为大写)。 \n-p 本服务器监听的端口(p为小写)。" << endl;
    return 0;
}

static std::string AuthorizationModeToString(AuthorizationMode e) {
    switch (e) {
    case EAM_Unknown:
        return "EAM_Unknown";
    case EAM_None:
        return "EAM_None";
    case EAM_Password:
        return "EAM_Password";
    default:
        return "Unknown(NOT SET?)";
    }
}

int fd_listen_socket = -1;

// 收到SIGINT信号退出程序。
volatile sig_atomic_t app_quit_flag = 0;
 static void sig_int_handler(int sig)
 {
     sig = sig;
     app_quit_flag = 1;
     if (fd_listen_socket != -1){
         close(fd_listen_socket);
     }
 }

std::string g_config_file = "/tmp/ssrv/ssrv.config";
void run_crash_handler();
int main(int argc, char** argv)
{
    signal(SIGINT, sig_int_handler);
    run_crash_handler();
    cout << "server app start." << endl;
    setlocale(LC_ALL, "");

    qcutil::Logger::getInstance()->setLogFileName("/tmp/s5.log");
    qcutil::Logger::getInstance()->setIsPrintStdOutput(true);

    int ch = 0;
    //socks5 default port.
    int port = 1080;
    std::string username;
    std::string password;
    while(-1 != (ch = getopt(argc, argv, ":p:u:P:")))
    {
        switch(ch)
        {
        case 'p':
            //p for port.
            port = atoi(optarg);
            break;
        case 'u':
            //u for user name.
            username = optarg;
            break;
        case 'P':
            //Capital P for password.
            password = optarg;
            break;
        case ':':
            cerr << "error: option -" << optopt << " requires an operand" << endl;
            /* fall through */
        case '?':
            return usage();
        }
    }

    if ((!username.empty() && password.empty()) || (!password.empty() && username.empty()))
    {
        cerr << "error: user name & password must use togather!" << endl;
        return 1;
    }

    std::string config_file = g_config_file;
    qcutil::File::createPath(config_file);

    Json::Value jv;
    if (username.empty() && password.empty()){
        jv["auth_type"] = "none"; //"none" or "password"
    } else {
        jv["auth_type"] = "password";
        jv["user_name"] = username;
        jv["password"] = password;
    }
    jv["port"] = port;
    qcutil::FileWriter fw(config_file);
    if(fw.open())
    {
        fw.write(jv.toStyledString());
        fw.close();
    }
    else
    {
        cerr << "Error!Update configure file failed." << endl;
        return 1;
    }


    CSocks5App s5;
    s5.load_config();

    cout << "server option: username = " << username <<", password = " << password << ", port = " << port << endl;
    cout << "auth mode: "<< AuthorizationModeToString(CAuthType::getInstance().getAuthorizationMode())
         << ",user: " << CAuthType::getInstance().getUserName()
         << ",password: " << CAuthType::getInstance().getPassword() << endl;

    std::string bind_ip("0.0.0.0");
    addrinfo* px = NULL;
    if (0 != resolve(bind_ip.c_str(), port, &px))
    {
        cerr << "resolve " << bind_ip << ":" << port << "failed with error:"
             << errno << endl;
        return 1;
    }

    addrinfo* pn = px;
    while (pn)
    {
        fd_listen_socket = socket(pn->ai_family, pn->ai_socktype, pn->ai_protocol);
        if (fd_listen_socket < 0)
        {
            pn = pn->ai_next;
            continue;
        }
        int yes = 1;
        setsockopt(fd_listen_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        if(bind(fd_listen_socket, pn->ai_addr, pn->ai_addrlen) < 0)
        {
            cerr << "bind failed with error:" << errno << endl;
            close(fd_listen_socket);
            fd_listen_socket = -1;
            pn = pn->ai_next;
            continue;
        }
        break;
    }

    freeaddrinfo(px);

    if (fd_listen_socket < 0)
    {
        cerr << "no socket could use to listen." << endl;
        return 1;
    }

    if (0 != listen(fd_listen_socket, SOMAXCONN))
    {
        cerr << "listen socket failed with error: " << errno << endl;
    }


    while(!app_quit_flag)
    {
        sockaddr_union s;
        socklen_t l = sizeof(s);
        int new_sock = accept(fd_listen_socket, (sockaddr*)&s, &l);
        if (new_sock < 0) {
            if (EINTR == errno)
                continue; // break?
            else
            {
                cerr << "accept() failed with errno: " << errno << endl;
            }
        }

        if (new_sock > 0)
        {
            cout << "accept new connection, fd: " << new_sock << endl;
            s5.socket_accepted(new_sock);
            s5.check();
        }
    }

    if (app_quit_flag)
    {
        s5.stop();
    }

    return 0;
}
